/* 
 * File:   cPhisycs.cpp
 * Author: josep
 * 
 * Created on 14 / desembre / 2010, 02:57
 */

#include "cPhysics.h"
#include "Globals.h"
#include "cLateralAgent.h"
#include "cTiledScene.h"
#include "cCollisionEvent.h"
#include "cAgentCollisionEvent.h"
#include "cVisionEvent.h"
#include "cItemCollisionEvent.h"

cPhysics::cPhysics() {
    Origin[X]=0;
    Origin[Y]=0;
    GravitationalAcceleration=DEFAULT_GRAVITATIONAL_ACCELERATION;
    DefaultFrictionFactor=DEFAULT_FRICTION_FACTOR;
}

cPhysics::cPhysics(int DImensionX, int DImensionY, int OriginX, int OriginY){
    Dimension[X]=DImensionX;
    Dimension[Y]=DImensionY;
    Origin[X]=OriginX;
    Origin[Y]=OriginY;
}


cPhysics::cPhysics(const cPhysics& orig) {
}

cPhysics::~cPhysics() {
}

void cPhysics::Init(int DImensionX, int DImensionY, int visibility
                                , int OriginX, int OriginY){

    Dimension[X]=DImensionX;
    Dimension[Y]=DImensionY;
    Origin[X]=OriginX;
    Origin[Y]=OriginY;
    this->visibility=visibility;
    Init();
}

void cPhysics::Init(){
    int size=visibility*2+1;
    visionMatrix.resize(size);
    for(int i=0; i<size; i++){
        visionMatrix[i].resize(visibility*2+1);
    }
}

void cPhysics::ProcessNextStep(cPhysicalObject* PhysicalObject, int time){
    int collisionType;

    //aplicar dinàmica
    if(PhysicalObject->IsInTheAir()){
        //aplicar fuerza de gravedad
        PhysicalObject->IncSpeedY(-GetGravitationalAcceleration());
    }else{
        /*aplicar simulación de fricción. 
         * aplicar fuerza plano inclinado
         * TO DO ....
         */
    }

    {
    //Generar mapa visible a partir de la posición del objeto
    int tile_x0 = PhysicalObject->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
    int tile_y0 = PhysicalObject->GetPosY() / Scene->GetTileSize();
    //omplir amb els elements visibles de l'escena

    for(int x=-visibility; x<=visibility; x++){
        for(int y=-visibility; y<=visibility; y++){
			if(tile_x0+x>=0 && tile_y0+y>=0 && tile_x0+x<Scene->GetScenWidth()
										&& tile_y0+y <Scene->GetSceneHeight()){
                visionMatrix[visibility+x][visibility+y]=
					(*(Scene->GetMap()))[
						tile_x0 + x + (tile_y0 + y)*(Scene->GetScenWidth())
					];
            }else{
                visionMatrix[visibility+x][visibility+y]=0;
            }
        }
    }
    //llenar con los objetos no agentes
    //si mantuvieramos una matriz (o un map) com las coordenadas de cada objeto i agente no
    //haria falta passar por todos. Implementar más adelante ( TO DO ...)
    for(list<cPositionedObject*>::iterator it=objects->begin();
        it!=objects->end(); it++){
        int obj_tile_x = (*it)->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
        int obj_tile_y = (*it)->GetPosY() / Scene->GetTileSize();
        int difx =obj_tile_x-tile_x0;
        int dify =obj_tile_y-tile_y0;
        if(difx>=-visibility && difx<=visibility && dify>=-visibility && dify<=visibility){
            visionMatrix[visibility+difx][visibility+dify]=(*it)->GetObjetctType();
        }
    }

    //llenar con los objetos agentes
    //si mantuvieramos una matriz (o un map) com las coordenadas de cada objeto i agente no
    //haria falta passar por todos. Implementar más adelante ( TO DO ...)
    for(list<cPhysicalObject*>::iterator it=agents->begin();
        it!=agents->end(); it++){
        int obj_tile_x = (*it)->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
        int obj_tile_y = (*it)->GetPosY() / Scene->GetTileSize();
        int difx =obj_tile_x-tile_x0;
        int dify =obj_tile_y-tile_y0;
        if(difx>=-visibility && difx<=visibility && dify>=-visibility && dify<=visibility){
            visionMatrix[visibility+difx][visibility+dify]=(*it)->GetObjetctType();
        }
    }

    //llenar con el player
    if(PhysicalObject!=Player){
        int obj_tile_x = Player->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
        int obj_tile_y = Player->GetPosY() / Scene->GetTileSize();
        int difx =obj_tile_x-tile_x0;
        int dify =obj_tile_y-tile_y0;
        if(difx>=-visibility && difx<=visibility && dify>=-visibility && dify<=visibility){
            visionMatrix[visibility+difx][visibility+dify]=Player->GetObjetctType();
        }
    }

    //Notoficar visión;
    PhysicalObject->Notify(&(cVisionEvent(&visionMatrix)));

    //Permitir la acción del agente
    PhysicalObject->NextStep(time);
    }

    /*CORREGIR LA ACCIÓN*/
  //Generar mapa visible a partir de la posición del objeto
    int tile_x = PhysicalObject->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
    int tile_y = PhysicalObject->GetPosY() / Scene->GetTileSize();

    //comprobar limites del mundo
    if(PhysicalObject->GetPosX()<GetOrigin()[X]){
        //Paramos el objeto en el límite
        PhysicalObject->StopX(GetOrigin()[X]);
    }
    if(tile_x>=Scene->GetScenWidth()){
        //Paramos el objeto en el límite
        PhysicalObject->StopX(Scene->GetScenWidth()*Scene->GetTileSize());
    }

    //comprobar limites del mundo
    if(PhysicalObject->GetPosY()<GetOrigin()[Y]){
        //Paramos el objeto en el límite
        PhysicalObject->StopY(GetOrigin()[Y]);
    }
    if(tile_y>=Scene->GetSceneHeight()){
        //Paramos el objeto en el límite
        PhysicalObject->StopY(Scene->GetSceneHeight()*Scene->GetTileSize());
    }
    
    //comprobar si sobrepassa los limites de la escena
    if(PhysicalObject->GetPosY()>Scene->GetSceneHeight()*Scene->GetTileSize()){
        PhysicalObject->StopY(Scene->GetSceneHeight()*Scene->GetTileSize());
    }

    //comprobar si colisiona con el techo
    collisionType=CollidesMapRoof(PhysicalObject, Scene);
    PhysicalObject->Notify(&cCollisionEvent(collisionType));

    //comprobar si colisiona con el suelo
    CollidesMapFloor(PhysicalObject, Scene);
    //COmprobar las paredes
    collisionType=CollidesMapWall(PhysicalObject, Scene);
    PhysicalObject->Notify(&cCollisionEvent(collisionType));


    //comprobar si colisiona con los objetos del mundo
    for(list<cPositionedObject*>::iterator it=objects->begin(); it!=objects->end(); it++){
        int collision=PhysicalObject->GetArea().GetIntersectionType(&((*it)->GetArea()));
        if(collision!=c2DArea::EXTERN){
            //PhysicalObject
            PhysicalObject->Notify( &(cItemCollisionEvent(collision, (*it))) );
        }
    }

    //comprobar si colisiona con los agentes del mundo
    for(list<cPhysicalObject*>::iterator it=agents->begin(); it!=agents->end(); it++){
        if(PhysicalObject!=(*it)){
            CollidesPhysicalObject((*it), PhysicalObject);
        }
    }

}

void cPhysics::SetOriginX(int Origin) {
    this->Origin[X] = Origin;
}
void cPhysics::SetOriginY(int Origin) {
    this->Origin[Y] = Origin;
}
const int* cPhysics::GetOrigin() const {
    return Origin;
}
void cPhysics::SetDimensionX(int Dimension) {
    this->Dimension[X] = Dimension;
}
void cPhysics::SetDimensionY(int Dimension) {
    this->Dimension[Y] = Dimension;
}
const int* cPhysics::GetDimension() const {
    return Dimension;
}
void cPhysics::SetDefaultFrictionFactor(float DefaultFrictionFactor) {
    this->DefaultFrictionFactor = DefaultFrictionFactor;
}
float cPhysics::GetDefaultFrictionFactor() const {
    return DefaultFrictionFactor;
}
void cPhysics::SetGravitationalAcceleration(float GravitationalAcceleration) {
    this->GravitationalAcceleration = GravitationalAcceleration;
}
float cPhysics::GetGravitationalAcceleration() const {
    return GravitationalAcceleration;
}
void cPhysics::SetVisibility( int visibility) {
    this->visibility = visibility;
}
int cPhysics::GetVisibility() const {
    return visibility;
}
void cPhysics::SetScene(cTiledScene* Scene) {
    this->Scene = Scene;
}
void cPhysics::SetAgents(list<cPhysicalObject*>* agents) {
    this->agents = agents;
}
void cPhysics::SetObjects(list<cPositionedObject*>* objects) {
    this->objects = objects;
}
void cPhysics::SetPlayer(cLateralPlayer* Player) {
    this->Player = Player;
}
void cPhysics::CollidesMapFloor(cPhysicalObject *PhysicalObject, cTiledScene *Scene/*int *map*/){
        int reac;
        int tile_x,tile_y;
	int width_tiles;
	bool on_base;
        bool isWall=false;
        bool isFloor=true;
	int i;

        if(PhysicalObject->GetSpeedX()==0){
            reac=0;
        }else if(PhysicalObject->GetSpeedX()>0){
            reac=-1;
        }else{
            reac=1;
        }

	tile_x = PhysicalObject->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
	tile_y = PhysicalObject->GetPosY() / Scene->GetTileSize();

	width_tiles = PhysicalObject->GetWidth() / Scene->GetTileSize();

	if( (PhysicalObject->GetPosX()%Scene->GetTileSize())!= 0){
            width_tiles++;
        }

	on_base = false;
	i=0;
	while((i<width_tiles) && !on_base){
            if( (PhysicalObject->GetPosY()%Scene->GetTileSize())== 0 ){
                //isWall = (*(Scene->GetMap()))[ tile_x + i + reac + ((tile_y-1)*Scene->GetScenWidth()) ] == 0;
                //isFloor = (*(Scene->GetMap()))[ tile_x + i + ((tile_y)*Scene->GetScenWidth()) ] == 0;
                if(tile_y>0
						&& (*(Scene->GetMap()))[ (tile_x + i) + ((tile_y - 1) * Scene->GetScenWidth()) ]!=0
                        && isFloor && !isWall){
                            on_base = true;
                }
            }else{
                //isWall = (*(Scene->GetMap()))[ tile_x + i + reac + ((tile_y)*Scene->GetScenWidth()) ] == 0;
				isFloor=false;
				if((tile_x + i + (tile_y + 1)*Scene->GetScenWidth())
							<16500){
                isFloor = (*(Scene->GetMap()))[ tile_x + i + ((tile_y + 1)*Scene->GetScenWidth()) ] == 0;
				}

                if((*(Scene->GetMap()))[ (tile_x + i) + (tile_y * Scene->GetScenWidth()) ] != 0
                        && isFloor && !isWall){
                            PhysicalObject->StopY((tile_y + 1) * Scene->GetTileSize());
                            on_base = true;
                }
            }
            i++;
	}
        PhysicalObject->SetInTheAir(!on_base);
}


int cPhysics::CollidesMapRoof(cPhysicalObject *PhysicalObject, cTiledScene *Scene){
	int tile_x,tile_y;
	int width_tiles,height_tiles;
	int i;

        if(PhysicalObject->GetSpeedY()<=0){
            return cCollisionEvent::ALL_RIGHT;
        }

	tile_x = PhysicalObject->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
	tile_y = PhysicalObject->GetPosY() / Scene->GetTileSize();

	width_tiles = PhysicalObject->GetWidth() / Scene->GetTileSize();
        height_tiles = PhysicalObject->GetHeight() / Scene->GetTileSize();

        tile_y += height_tiles;

	if( (PhysicalObject->GetPosX()%Scene->GetTileSize())!= 0){
            width_tiles++;
        }

	i=0;
	while(i<width_tiles && (tile_x+i)<Scene->GetScenWidth()){
		if((*(Scene->GetMap()))[ (tile_x + i) + ((tile_y)*Scene->GetScenWidth()) ] != 0){
                   PhysicalObject->StopY( (tile_y-1) * Scene->GetTileSize()+1);
                   return cCollisionEvent::NORTH_COLLISION;
                }
		i++;
	}
        return cCollisionEvent::ALL_RIGHT;
}

int cPhysics::CollidesMapWall(cPhysicalObject *PhysicalObject, cTiledScene *Scene){
        bool right;
	int tile_x,tile_y;
	int j;
	int width_tiles,height_tiles;
        int collisionType;
        int reac;

        if(PhysicalObject->GetSpeedX()==0){
            return cCollisionEvent::ALL_RIGHT;
        }

        right=(PhysicalObject->GetSpeedX()>0);

        tile_x = PhysicalObject->GetPosX() / Scene->GetTileSize();//TILE_SIZE;
	tile_y = PhysicalObject->GetPosY() / Scene->GetTileSize();

	width_tiles = PhysicalObject->GetWidth() / Scene->GetTileSize();
        height_tiles = PhysicalObject->GetHeight() / Scene->GetTileSize();

	if(right){
            reac = -1;
            tile_x += width_tiles;
            collisionType=cCollisionEvent::EAST_COLLISION;
        }else{
            reac = 1;
            collisionType=cCollisionEvent::WEST_COLLISION;
        }

	for(j=0;j<height_tiles && (tile_y+j)<Scene->GetSceneHeight();j++){
            //bool isFloor = (*(Scene->GetMap()))[ tile_x + ((tile_y+j+1)*Scene->GetScenWidth()) ] == 0;
            //bool isWall = (*(Scene->GetMap()))[ tile_x + reac + ((tile_y+j)*Scene->GetScenWidth()) ] == 0;
            if((*(Scene->GetMap()))[ tile_x + ((tile_y+j)*Scene->GetScenWidth()) ] != 0
                   /*&& isWall && !isFloor*/) {
               PhysicalObject->StopX((tile_x + reac) * Scene->GetTileSize());
               return collisionType;
            }
	}
        return cCollisionEvent::ALL_RIGHT;
}

void cPhysics::CollidesPhysicalObject(cPhysicalObject* PhysicalObject1, cPhysicalObject* PhysicalObject2){
    c2DArea area1=PhysicalObject1->GetArea();
    c2DArea area2=PhysicalObject2->GetArea();

    //cout <<"it dir: " << collision << "\n";
    int collision=CollidesArea(&area1, &area2);
    //cout << PhysicalObject1->GetNom() << " cor: " << collision << "\n";
    if(collision!=c2DArea::EXTERN){
        PhysicalObject1->Notify(&cAgentCollisionEvent(collision,
                                            PhysicalObject2->GetSurfaceType(),
                                            &area2, PhysicalObject2->isDead()));
    }

    collision=CollidesArea(&area2, &area1);
    //cout << PhysicalObject2->GetNom() << " cor: " << collision << "\n";
    if(collision!=c2DArea::EXTERN){
        PhysicalObject2->Notify(&cAgentCollisionEvent(collision,
                                            PhysicalObject1->GetSurfaceType(),
                                            &area1, PhysicalObject1->isDead()));
    }
}

int cPhysics::CollidesArea(c2DArea* area1, c2DArea* area2){
    float dif= area1->GetSpeedY()-area2->GetSpeedY();
    int collision=area1->GetIntersectionType(area2);
    if(collision!=c2DArea::EXTERN){
        //restamos las velocidades para saber la dirección relativa de
        //un agente respecto al otro
        //cout <<"it dir: " << collision << "\n";
        if(dif==0){
            collision&=c2DArea::INCLUDE_NORTH^collision;
            //cout <<"-N. it cor: " << collision << "\n";
            collision&=c2DArea::INCLUDE_SOUTH^collision;
            //cout <<"-S. it cor: " << collision << "\n";
        }else if(dif>0){
            collision&=c2DArea::INCLUDE_NORTH^collision;
            //cout <<"-N. it cor: " << collision << "\n";
        }else{
            collision&=c2DArea::INCLUDE_SOUTH^collision;
            //cout <<"-S. it cor: " << collision << "\n";
        }
        dif= area1->GetSpeedX()-area2->GetSpeedX();
        if(dif==0){
            collision&=c2DArea::INCLUDE_EAST^collision;
            //cout <<"-E. it cor: " << collision << "\n";
            collision&=c2DArea::INCLUDE_WEST^collision;
            //cout <<"-W. it cor: " << collision << "\n";
        }else if(dif>0){
            collision&=c2DArea::INCLUDE_WEST^collision;
            //cout <<"-W. it cor: " << collision << "\n";
        }else{
            collision&=c2DArea::INCLUDE_EAST^collision;
            //cout <<"-E. it cor: " << collision << "\n";
        }
    }
    return collision;
}